package ar.edu.caece.compiler.grammar.factories;

import java.util.ArrayList;

import ar.edu.caece.compiler.error.ErrorManager;
import ar.edu.caece.compiler.error.ParsingException;
import ar.edu.caece.compiler.grammar.AbstractTokenVisitorFactory;
import ar.edu.caece.compiler.grammar.Recognizer;
import ar.edu.caece.compiler.grammar.TokenRecognizer;
import ar.edu.caece.compiler.grammar.TokenVisitor;
import ar.edu.caece.compiler.grammar.attributes.Attribute;
import ar.edu.caece.compiler.grammar.attributes.AttributeSymbolTable;
import ar.edu.caece.compiler.grammar.attributes.AttributeUtils;
import ar.edu.caece.compiler.grammar.attributes.BooleanAttribute;
import ar.edu.caece.compiler.grammar.attributes.DeclaracionFuncion;
import ar.edu.caece.compiler.grammar.attributes.DeclaracionParametro;
import ar.edu.caece.compiler.grammar.attributes.ListaParametrosAttribute;
import ar.edu.caece.compiler.grammar.attributes.ListaParametrosDeclaracionAttribute;
import ar.edu.caece.compiler.grammar.attributes.ListaVariablesAttribute;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeDeclaraciones;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeFuncProc;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeStaticUtilities;
import ar.edu.caece.compiler.grammar.nonterminals.ALTERP;
import ar.edu.caece.compiler.grammar.nonterminals.BLOQUE1;
import ar.edu.caece.compiler.grammar.nonterminals.NOMBREP;
import ar.edu.caece.compiler.grammar.nonterminals.PARAMETROS;
import ar.edu.caece.compiler.grammar.terminals.FinParentesisToken;
import ar.edu.caece.compiler.grammar.terminals.InicioParentesisToken;
import ar.edu.caece.compiler.grammar.terminals.ProcedimientoToken;
import ar.edu.caece.compiler.grammar.terminals.PuntoComaToken;

public class BLOQUE1Factory extends AbstractTokenVisitorFactory {

	private static BLOQUE1Factory INSTANCE;
	
	private NOMBREPFactory nombrepFactory;
	private PARAMETROSFactory parametrosFactory;

	private TokenVisitor parseNOMBREP;
	private ALTERPFactory alterpFactory;
	private TokenVisitor parseALTERP;
	private TokenVisitor parsePARAMETROS;
	
	private TokenVisitor parseInicioParentesisToken;
	private TokenVisitor parseFinParentesisToken;
	private TokenVisitor parsePuntoComaToken;

	
	public static BLOQUE1Factory getInstance() throws Exception {
		if(INSTANCE==null) INSTANCE = new BLOQUE1Factory();
		return INSTANCE;
	}
	
	private Initializer initializer = new Initializer() {
		
		@Override
		public void init() throws Exception {
			initializer = NoOperationInitializer.getInstance();
			nombrepFactory = NOMBREPFactory.getInstance();
			alterpFactory = ALTERPFactory.getInstance();
			parametrosFactory = PARAMETROSFactory.getInstance();
			parseNOMBREP = nombrepFactory.makeVisitor();
			parseALTERP = alterpFactory.makeVisitor();
			parsePARAMETROS = parametrosFactory.makeVisitor();
					
			parseInicioParentesisToken= new InicioParentesisToken.InicioParentesisTokenVisitor() {
				
				@Override
				public Recognizer defaultCase(TokenRecognizer llamador) throws Exception {
					ErrorManager.getInstance().manage(llamador,"InicioParentesisToken",new ParsingException("Error de parseo. Se recibi�: " +llamador.getToken(),InicioParentesisToken.class,llamador));
					return null;
				}
				
				@Override
				public Recognizer inicioParentesisCase(InicioParentesisToken llamador) {
					return llamador;
				}
			};
			
			parseFinParentesisToken= new FinParentesisToken.FinParentesisTokenVisitor() {
				
				@Override
				public Recognizer defaultCase(TokenRecognizer llamador) throws Exception {
					ErrorManager.getInstance().manage(llamador,"FinParentesisToken",new ParsingException("Error de parseo. Se recibi�: " +llamador.getToken(),FinParentesisToken.class,llamador));
					return null;
				}
				
				@Override
				public Recognizer finParentesisCase(FinParentesisToken llamador) {
					return llamador;
				}
			};
			
			parsePuntoComaToken= new PuntoComaToken.PuntoComaTokenVisitor() {
				
				@Override
				public Recognizer defaultCase(TokenRecognizer llamador) throws Exception {
					ErrorManager.getInstance().manage(llamador,"PuntoComaToken",new ParsingException("Error de parseo. Se recibi�: " +llamador.getToken(),PuntoComaToken.class,llamador));
					return null;
				}
				
				@Override
				public Recognizer puntoComaCase(PuntoComaToken llamador) {
					return llamador;
				}
			};
		}
	};
	
	private BLOQUE1Factory() throws Exception {

	}
	
	@Override
	public TokenVisitor makeChainedVisitor(
			final TokenVisitor siguiente) throws Exception {
		initializer.init();

		return new ProcedimientoToken.ProcedimientoChainVisitor(siguiente) {

			@Override
			public Recognizer procedimientoCase(ProcedimientoToken llamador) throws Exception {
				ListaParametrosDeclaracionAttribute listaParametros = new ListaParametrosDeclaracionAttribute();
				
				NOMBREP nombrep = (NOMBREP) nextToken().execute(parseNOMBREP);
				
				String thisLabel = null;//label a asignar al procedimiento actual

				InicioParentesisToken inicioParentesisToken = (InicioParentesisToken) nextToken().execute(parseInicioParentesisToken);
				parsePARAMETROS.addInheritedAttribute(Attribute.LISTA_PARAMETROS_HEREDADA, listaParametros);
				PARAMETROS parametros = (PARAMETROS) nextToken().execute(parsePARAMETROS);
				
				
				FinParentesisToken finParentesisToken = (FinParentesisToken) nextToken().execute(parseFinParentesisToken);
				PuntoComaToken puntoComaToken = (PuntoComaToken) nextToken().execute(parsePuntoComaToken);
				
				ListaVariablesAttribute variables;
				AttributeSymbolTable tablaSimbolos = (AttributeSymbolTable) getAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA);
				AttributeSymbolTable tablaSimbolosSintetizada = new AttributeSymbolTable();
				
				NodeFuncProc nodeFuncProc = new NodeFuncProc();

				if(parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)==null) {
					nodeFuncProc.setParametros(new ArrayList<DeclaracionParametro>());

					variables = new ListaVariablesAttribute();
					parametros.addAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA, variables);
				} else {
					variables = new ListaVariablesAttribute();
					AttributeUtils.agregarParametrosAListaVariables(variables,(ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				}
				
				if("principal".equalsIgnoreCase(nombrep.getAttribute(Attribute.LEXEMA).toString()) && 
						! ((ListaParametrosAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).isVacia() ) {
					//TODO ERROR
					AttributeUtils.error("Error. Principal no puede tener par�metros.",llamador);
				} else {
					if( !AttributeUtils.existe(nombrep.getAttribute(Attribute.LEXEMA).toString(), tablaSimbolos)) {
						AttributeUtils.agregarVariablesATablaSimbolos(tablaSimbolos, (ListaVariablesAttribute) variables);
						tablaSimbolosSintetizada = AttributeUtils.agregarProcedimiento(nombrep.getAttribute(Attribute.LEXEMA).toString(),(ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA),(AttributeSymbolTable) getAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA),null,NodeStaticUtilities.getFunctionLabelId());
						thisLabel = NodeStaticUtilities.lastAssignedLabel();
					} else {
						//TODO ERROR
						AttributeUtils.error("Error. Procedimiento/funci�n ya declarado",llamador);
					}
				}
				
				boolean ultimo = "principal".equalsIgnoreCase(nombrep.getAttribute(Attribute.LEXEMA).toString());
				
				parseALTERP.addInheritedAttribute("ultimo", new BooleanAttribute(ultimo));
				parseALTERP.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, tablaSimbolos);
				
				nodeFuncProc.setPrincipal(ultimo);
				
				nodeFuncProc.setNombreProc(nombrep.getAttribute(Attribute.LEXEMA).toString());
				NodeStaticUtilities.resetVariableNumber();
				nodeFuncProc.setLabel(thisLabel);
				nodeFuncProc.propagarLabel();
				NodeDeclaraciones declaraciones = AttributeUtils.declaracionesFromParams((ListaParametrosDeclaracionAttribute)parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				declaraciones.addDeclaracion(new DeclaracionFuncion(nombrep.getAttribute(Attribute.LEXEMA).toString(),null, false,((ListaParametrosDeclaracionAttribute)parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).getListaParametros(),thisLabel));

				nodeFuncProc.setDeclaraciones(declaraciones);
				nodeFuncProc.setParametros(((ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).getListaParametros());

				parseALTERP.addInheritedAttribute(Attribute.ARBOL_HEREDADO, nodeFuncProc);
				ALTERP alterp = (ALTERP) nextToken().execute(parseALTERP);
				BLOQUE1 bloque1 = new BLOQUE1(llamador,nombrep,inicioParentesisToken ,parametros, 
						finParentesisToken,puntoComaToken,
						alterp);

				bloque1.addAttribute("ultimo", new BooleanAttribute(ultimo));
				bloque1.addAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA, parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				bloque1.addAttribute(Attribute.TABLA_SIMBOLOS_SINTETIZADA, tablaSimbolosSintetizada);
				
				System.out.println("BLOQUE1: " + alterp);
				
				nodeFuncProc = (NodeFuncProc) alterp.getAttribute(Attribute.ARBOL_SINTETIZADO);
				nodeFuncProc.setTablaSimbolos(tablaSimbolosSintetizada);
				nodeFuncProc.propagarALlamadas();
				bloque1.addAttribute(Attribute.ARBOL_SINTETIZADO, nodeFuncProc);
				return bloque1;
			}
		};
	
	}
	@Override
	public TokenVisitor makeVisitor() throws Exception {
		initializer.init();

		return new ProcedimientoToken.ProcedimientoTokenVisitor() {
			
			@Override
			public Recognizer defaultCase(TokenRecognizer llamador) throws Exception {
				ErrorManager.getInstance().manage(llamador,"BLOQUE1",new ParsingException("Error de parsing.",BLOQUE1.class,llamador));
				return null;
			}

			@Override
			public Recognizer procedimientoCase(ProcedimientoToken llamador)
					throws Exception {
				ListaParametrosDeclaracionAttribute listaParametros = new ListaParametrosDeclaracionAttribute();
				
				NOMBREP nombrep = (NOMBREP) nextToken().execute(parseNOMBREP);
				
				String thisLabel = null;//label que va a tener el procedimiento actual.
				
				InicioParentesisToken inicioParentesisToken = (InicioParentesisToken) nextToken().execute(parseInicioParentesisToken);
				parsePARAMETROS.addInheritedAttribute(Attribute.LISTA_PARAMETROS_HEREDADA, listaParametros);
				PARAMETROS parametros = (PARAMETROS) nextToken().execute(parsePARAMETROS);
				
				
				FinParentesisToken finParentesisToken = (FinParentesisToken) nextToken().execute(parseFinParentesisToken);
				PuntoComaToken puntoComaToken = (PuntoComaToken) nextToken().execute(parsePuntoComaToken);
				
				ListaVariablesAttribute variables;
				
				AttributeSymbolTable tablaSimbolos = (AttributeSymbolTable) getAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA);
				AttributeSymbolTable tablaSimbolosSintetizada = new AttributeSymbolTable();
				
				NodeFuncProc nodeFuncProc = new NodeFuncProc();

				if(parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)==null) {
					nodeFuncProc.setParametros(new ArrayList<DeclaracionParametro>());

					variables = new ListaVariablesAttribute();
					parametros.addAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA, variables);
				} else {
					variables = new ListaVariablesAttribute();
					AttributeUtils.agregarParametrosAListaVariables(variables,(ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				}
				
				if("principal".equalsIgnoreCase(nombrep.getAttribute(Attribute.LEXEMA).toString()) && 
						! ((ListaParametrosAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).isVacia() ) {
					//TODO ERROR
					AttributeUtils.error("Error. Principal no puede tener par�metros.",llamador);
				} else {
					if( !AttributeUtils.existe(nombrep.getAttribute(Attribute.LEXEMA).toString(), tablaSimbolos)) {
						AttributeUtils.agregarVariablesATablaSimbolos(tablaSimbolos, (ListaVariablesAttribute) variables);
						tablaSimbolosSintetizada = AttributeUtils.agregarProcedimiento(nombrep.getAttribute(Attribute.LEXEMA).toString(),(ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA),(AttributeSymbolTable) getAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA),null,NodeStaticUtilities.getFunctionLabelId());
						thisLabel = NodeStaticUtilities.lastAssignedLabel();
					} else {
						//TODO ERROR
						AttributeUtils.error("Error. Procedimiento/funci�n ya declarado",llamador);
					}
				}
				
				boolean ultimo = "principal".equalsIgnoreCase(nombrep.getAttribute(Attribute.LEXEMA).toString());
				
				parseALTERP.addInheritedAttribute("ultimo", new BooleanAttribute(ultimo));
				parseALTERP.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, tablaSimbolos);
				
				nodeFuncProc.setPrincipal(ultimo);
				nodeFuncProc.setNombreProc(nombrep.getAttribute(Attribute.LEXEMA).toString());
				NodeStaticUtilities.resetVariableNumber();
				nodeFuncProc.setLabel(thisLabel);
				nodeFuncProc.propagarLabel();
				NodeDeclaraciones declaraciones = AttributeUtils.declaracionesFromParams((ListaParametrosDeclaracionAttribute)parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				declaraciones.addDeclaracion(new DeclaracionFuncion(nombrep.getAttribute(Attribute.LEXEMA).toString(),null, false,((ListaParametrosDeclaracionAttribute)parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).getListaParametros(),thisLabel));
				nodeFuncProc.setParametros(((ListaParametrosDeclaracionAttribute) parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA)).getListaParametros());

				nodeFuncProc.setDeclaraciones(declaraciones);
				
				parseALTERP.addInheritedAttribute(Attribute.ARBOL_HEREDADO, nodeFuncProc);
				
				ALTERP alterp = (ALTERP) nextToken().execute(parseALTERP);
				BLOQUE1 bloque1 = new BLOQUE1(llamador,nombrep,inicioParentesisToken ,parametros, 
						finParentesisToken,puntoComaToken,
						alterp);

				bloque1.addAttribute("ultimo", new BooleanAttribute(ultimo));
				bloque1.addAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA, parametros.getAttribute(Attribute.LISTA_PARAMETROS_SINTETIZADA));
				bloque1.addAttribute(Attribute.TABLA_SIMBOLOS_SINTETIZADA, tablaSimbolosSintetizada);
				
				nodeFuncProc = (NodeFuncProc) alterp.getAttribute(Attribute.ARBOL_SINTETIZADO);
				nodeFuncProc.setTablaSimbolos(tablaSimbolosSintetizada);
				nodeFuncProc.propagarALlamadas();

				bloque1.addAttribute(Attribute.ARBOL_SINTETIZADO, nodeFuncProc);

				
				return bloque1;
			}
		};
	}
	
	
}
